/* Copyright (C) 2004 The Trustees of Indiana University. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 * 
 * 2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 * 
 * 3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 * 
 * "This product includes software developed by the Indiana University Extreme!
 * Lab. For further information please visit http://www.extreme.indiana.edu/"
 * 
 * Alternatively, this acknowledgment may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 * 
 * 4) The name "Indiana University" or "Indiana University Extreme! Lab" shall
 * not be used to endorse or promote products derived from this software without
 * prior written permission from Indiana University. For written permission,
 * please contact http://www.extreme.indiana.edu/.
 * 
 * 5) Products derived from this software may not use "Indiana University" name
 * nor may "Indiana University" appear in their name, without prior written
 * permission of the Indiana University.
 * 
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of any
 * other entity. Indiana University disclaims any liability to any recipient for
 * claims brought by any other entity based on infringement of intellectual
 * property rights or otherwise.
 * 
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */

package edu.indiana.cs.b534.torrent.context;

import edu.indiana.cs.b534.torrent.Constants;
import edu.indiana.cs.b534.torrent.StorageManager;
import edu.indiana.cs.b534.torrent.TorrentMetainfo;
import edu.indiana.cs.b534.torrent.impl.InfoCollector;
import edu.indiana.cs.b534.torrent.impl.PeerInstance;
import edu.indiana.cs.b534.torrent.impl.PieceRequestSender;
import edu.indiana.cs.b534.torrent.message.PeerDictionary;
import edu.indiana.cs.b534.torrent.struct.TorrentMetainfoImpl;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

/**
 * @author : Eran Chinthaka (echintha@cs.indiana.edu)
 * @Date : Feb 17, 2007
 */
public class TorrentContext extends AbstractContext {
    private int requestPipeLineSize = 5;
    private int maxpeersCount = 5;
    private int piecesCount = 0;
    private boolean isSeed = true;
    private File seedDataFile;
    private String myPeerId;
    private String torrentFile;
    private TorrentMetainfo torrentMetainfo;
    private int chokingPolicy = 0;
    private double shareRatio = 0.5;
    private boolean pipeLineInBatch = false;

    private PieceRequestSender pieceRequestSender;
    private InfoCollector infoCollector;

    private int maxAllowedBusyConnections = Constants.MAX_BUSY_CONNECTIONS;

//    public static final String TORRENT_META_INFO = "torrent-meta-info";

    private Map<String, Object> properties = new Hashtable<String, Object>();

    /**
     * We need two peer list because it is possible that two connection could be created at same time
     * by two peers and then keys will clash unless we have two lists.
     * <p/>
     * incoming peer information - peer instance created when we got a request from the peer
     * outgoing peer information - we initiated the peer instance
     */
    private Map<String, PeerInstance> incomingPeerInformation = new Hashtable<String, PeerInstance>();
    private Map<String, PeerInstance> outgoingPeerInformation = new Hashtable<String, PeerInstance>();


    private Map<String, PeerDescription> peerDescriptions = new Hashtable<String, PeerDescription>();
    private int port;
    private byte[] infoHash;

    private int uploadRatePerMinute = 1024 * 1024 * 10; // 1 MB
    /**
     * This is where the downloaded files will be saved
     */
    private String destinationFolder;

    private Vector<PeerDictionary> avalibleNonConnectedPeers;


    public TorrentContext(Properties properties) throws IOException {
        this.isSeed = Constants.TRUE_VALUE.equals(properties.get("is-seed"));

        String seedDataFileStr = properties.getProperty("seed-file");
        if (seedDataFileStr != null) {
            this.seedDataFile = new File(seedDataFileStr);
        }
        String portAsStr = properties.getProperty("port");
        destinationFolder = properties.getProperty("destination-folder");
        myPeerId = properties.getProperty("peer-id");
        port = Integer.parseInt(portAsStr);

        String maxBusyConnections = properties.getProperty("max-busy-connections");
        if (maxBusyConnections != null && !"".equals(maxBusyConnections)) {
            try {
                this.maxAllowedBusyConnections = Integer.parseInt(maxBusyConnections);
            } catch (NumberFormatException e) {
                // entered choking policy is not valid. Need not do anything here
            }
        }

        String chokingPolicy = properties.getProperty("choking-policy");
        if (chokingPolicy != null && !"".equals(chokingPolicy)) {
            try {
                this.chokingPolicy = Integer.parseInt(chokingPolicy);
            } catch (NumberFormatException e) {
                // entered choking policy is not valid. Need not do anything here
            }
        }

        String shareRatio = properties.getProperty("share-ratio");
        if (shareRatio != null && !"".equals(shareRatio)) {
            try {
                this.shareRatio = Double.parseDouble(shareRatio);
            } catch (NumberFormatException e) {
                // entered share ratio is not valid. Need not do anything here
            }
        }

        String requestPipeLineSize = properties.getProperty("request-pipeline-size");
        if (requestPipeLineSize != null && !"".equals(requestPipeLineSize)) {
            try {
                this.requestPipeLineSize = Integer.parseInt(requestPipeLineSize);
            } catch (NumberFormatException e) {
                // entered share ratio is not valid. Need not do anything here
            }
        }
        String pipeLineInBatchStr = properties.getProperty("is-batch-pipeline");
        if (pipeLineInBatchStr != null && !"".equals(pipeLineInBatchStr)) {
            try {
                this.pipeLineInBatch = Boolean.parseBoolean(pipeLineInBatchStr);
            } catch (NumberFormatException e) {
                // entered share ratio is not valid. Need not do anything here
            }
        }


        infoCollector = new InfoCollector(this);

        torrentFile = properties.getProperty("torrent-meta-info");
        torrentMetainfo = TorrentMetainfoImpl.deserialize(new BufferedInputStream(new FileInputStream(torrentFile)));
        requestQueue = new RequestQueue();
        pieceRequestSender = new PieceRequestSender(this);
        pieceRequestSender.start();
        avalibleNonConnectedPeers = new Vector<PeerDictionary>();
    }

    public Map<String, PeerInstance> getIncomingPeerInformation() {
        return incomingPeerInformation;
    }

    public Map<String, PeerInstance> getOutgoingPeerInformation() {
        return outgoingPeerInformation;
    }

    /**
     * this will record the history of downloaded parts. From this we can know how many parts
     * we have downloaded from a particular peer.
     * Key - PeerId
     * Value - Count
     */
    private Map<String, Integer> pastDownloadedInfo = new Hashtable<String, Integer>();

    /**
     * This will hold the list of peers
     */
    private Map<String, Boolean> presentUploadList = new Hashtable<String, Boolean>();
    private Map<String, Boolean> presentDownloadList = new Hashtable<String, Boolean>();
    private StorageManager storageManager;
    private RequestQueue requestQueue;

    public Object getProperty(String key) {
        return this.properties.get(key);
    }

    public void setProperty(String key, Object value) {
        this.properties.put(key, value);
    }

    public void addPeer(PeerInstance peerInstance, boolean incoming) {
        if (incoming) {
            incomingPeerInformation.put(peerInstance.getID(), peerInstance);
        } else {
            outgoingPeerInformation.put(peerInstance.getID(), peerInstance);
        }
    }

//    public Map<String, PartInfo> getPartsInformation() {
//        return partsInformation;
//    }
//
//    public void addPartsInformation(String peerId, PartInfo partsInfo){
//        partsInformation.put(peerId, partsInfo);
//    }

    public StorageManager getStorageManager() {
        return storageManager;
    }

    public void addPeerDescription(PeerDescription peerDescription) {
        peerDescriptions.put(peerDescription.getPeerId(), peerDescription);
    }

    public PeerDescription getPeerDescription(String peerID) {
        return peerDescriptions.get(peerID);
    }

    public Collection<PeerDescription> getPeerDescriptions() {
        return peerDescriptions.values();
    }

    public void setStorageManager(StorageManager storageManager) {
        this.storageManager = storageManager;
    }

    public int getMaxpeersCount() {
        return maxpeersCount;
    }

    public void setPiecesCount(int piecesCount) {
        this.piecesCount = piecesCount;
    }

    public int getPiecesCount() {
        return piecesCount;
    }

    public boolean isSeed() {
        return isSeed;
    }

    public void setSeed(boolean isSeed) {
        this.isSeed = isSeed;
    }

    public File getSeedDataFile() {
        return seedDataFile;
    }

    public void setSeedDataFile(File seedDataFile) {
        this.seedDataFile = seedDataFile;
    }

    public int getPort() {
        return port;
    }


    public String getMyPeerId() {
        return myPeerId;
    }


    public void setInfoHash(byte[] infoHash) {
        this.infoHash = infoHash;
    }

    public byte[] getInfoHash() {
        return infoHash;
    }

    public void removePeer(String connectingPeerId) {
    	synchronized (this) {
            if (this.incomingPeerInformation.containsKey(connectingPeerId)) {
                this.incomingPeerInformation.remove(connectingPeerId);
            }

            if (this.outgoingPeerInformation.containsKey(connectingPeerId)) {
                this.outgoingPeerInformation.remove(connectingPeerId);
            }
            peerDescriptions.remove(connectingPeerId);
		}
    }


    public String getDestinationFolder() {
        return destinationFolder;
    }


    public TorrentMetainfo getTorrentMetainfo() {
        return torrentMetainfo;
    }

    public void setTorrentMetainfo(TorrentMetainfo torrentMetainfo) {
        this.torrentMetainfo = torrentMetainfo;
    }

    public String getTorrentFile() {
        return torrentFile;
    }

    public void setTorrentFile(String torrentFile) {
        this.torrentFile = torrentFile;
    }

    public InfoCollector getInfoCollector() {
        return infoCollector;
    }

    // following information will provide the tracker some status about our upload and download status
    public long getTotalDownloadedBytes() {
        return storageManager.getTotalDownloadedBytes();
    }

    public long getTotalUploadedBytes() {
        return storageManager.getTotalUploadedBytes();
    }

    public long getBytesLeftToBeDownloaded() {
        return storageManager.getBytesLeftToBeDownloaded();
    }

    public RequestQueue getRequestQueue() {
        return requestQueue;
    }

    public int getUploadRatePerMinute() {
        return uploadRatePerMinute;
    }

    public void setUploadRatePerMinute(int uploadRatePerMinute) {
        this.uploadRatePerMinute = uploadRatePerMinute;
    }

    public Vector<PeerDictionary> getAvalibleNonConnectedPeers() {
        return avalibleNonConnectedPeers;
    }

//    public void removePeerDesciption(String peerID) {
//        peerDescriptions.remove(peerID);
//    }


    public int getChokingPolicy() {
        return chokingPolicy;
    }

    public double getShareRatio() {
        return shareRatio;
    }

    public int getRequestPipeLineSize() {
        return requestPipeLineSize;
    }

    public boolean isPipeLineInBatch() {
        return pipeLineInBatch;
    }


    public int getMaxAllowedBusyConnections() {
        return maxAllowedBusyConnections;
    }
}
